from a115.utils.mathutils import dcml, randint
from a115.utils.listutils import map0_n, ego
from dateutil import parser, relativedelta
import datetime, calendar

__all__ = ["datetime", "calendar", "date", "now", "today", "reldelta", "reldays", "one_day", "relmonths", "one_month", "relyears", "one_year", "bg2datetime", "bg2date", "dateratio", "smart_date_ratio", "split_period", "date2bg", "egn2bday", "egn2age", "egn2age_exact", "week_of", "start_week", "end_week", "week_range", "month_length", "start_month", "end_month", "month_of", "month_range", "date_range", "add_to_date", "reldelta_tuple", "date_delta", "random_date", "mdelta", "MIN_DATE", "MAX_DATE"]

date = datetime.date
MIN_DATE = date(datetime.MINYEAR, 1, 1)
MAX_DATE = date(datetime.MAXYEAR, 12, 31)
now = datetime.datetime.now
today = date.today
reldelta = relativedelta.relativedelta
mdelta = lambda s,e: reldelta(s,e).years*12+reldelta(s,e).months
reldays = lambda d: reldelta(days=d)
one_day = reldays(1)
relmonths = lambda m: reldelta(months=m)
one_month = relmonths(1)
relyears = lambda y: reldelta(years=y)
one_year = relyears(1)
bg2datetime = lambda d: parser.parse(d, dayfirst=True, fuzzy=True)
bg2date = lambda d: bg2datetime(d).date()
dateratio = lambda a,b,c,d: (b-a).days / dcml((d-c).days)

def split_period_(t, s, e, f=False):
    while s<=e:
        yield s
        s = (s + relmonths(12/t)) if t else MAX_DATE
split_period = lambda t, s, e, f=False: map(start_month if f else ego, split_period_(t, s, e))

def smart_date_ratio(a,b,c,d):
    r1 = reldelta(b,a)
    r2 = reldelta(d,c)
    if not any([r1.days, r1.months, r2.days, r2.months]):
        return r1.years / dcml(r2.years)
    if not any([r1.days, r2.days]):
        return (r1.years*12+r1.months) / dcml(r2.years*12+r2.months)
    return dateratio(a,b,c,d)

def date2bg(dt_obj, format = None):
    """ Formats a date object in BG date string.  """
    if isinstance(dt_obj, datetime.datetime):
        return dt_obj.strftime(format if format else "%d.%m.%Y %H:%M")
    elif isinstance(dt_obj, date):
        return dt_obj.strftime(format if format else "%d.%m.%Y")
    return dt_obj

def egn2bday(egn):
    year, month, day = map(int, [egn[:2],egn[2:4],egn[4:6]])
    if month > 40:
        month -= 40
        year += 2000
    elif month > 20:
        month -= 20
        year += 1800
    else:
        year += 1900
    return date(year, month, day)

egn2age = lambda e, as_of=None: reldelta(as_of or today(), egn2bday(e)).years
egn2age_exact =  lambda e, as_of=None: reldelta(as_of or today(), egn2bday(e))

def week_of(dt_obj):
    wday = dt_obj.weekday()
    return map0_n(lambda i: dt_obj+reldays(i-wday), 7)
start_week = lambda x: week_of(x)[0]
end_week = lambda x: week_of(x)[-1]
week_range = lambda x: (start_week(x), end_week(x))
month_length = lambda x: calendar.monthrange(x.year, x.month)[1]
start_month = lambda x: date(x.year, x.month, 1)
end_month = lambda x: date(x.year, x.month, month_length(x))
month_of = lambda d: map0_n(lambda i:start_month(d)+reldays(i),month_length(d))
month_range = lambda x: (start_month(x), end_month(x))
date_range = lambda s,e: map0_n(lambda i: s+reldays(i), (e-s).days+1)
add_to_date = lambda s,y,m,d: s+reldelta(years=y, months=m, days=d)
reldelta_tuple = lambda rd: (rd.years, rd.months, rd.days)
date_delta = lambda s,e: reldelta_tuple(reldelta(e, s))

def random_date(start_date=MIN_DATE, end_date=MAX_DATE):
    if start_date > end_date:
        return False
    year = randint(start_date.year, end_date.year)
    mo_low = 1
    mo_high = 12
    if year == end_date.year:
        mo_high = end_date.month
    if year == start_date.year:
        mo_low = start_date.month
    month = randint(mo_low, mo_high)
    day_low = 1
    day_high = calendar.monthrange(year, month)[1]
    if (year==end_date.year) and (month==end_date.month): 
        day_high = end_date.day
    if (year==start_date.year) and (month==start_date.month): 
        day_low = start_date.day
    day = randint(day_low, day_high)
    return date(year, month, day)

